/**
 *
 */
package org.simplextensions.registry;

import org.simplextensions.annotations.ExtensionStyle;
import org.simplextensions.annotations.lifecycle.Add;
import org.simplextensions.annotations.lifecycle.Remove;
import org.simplextensions.annotations.lifecycle.Start;
import org.simplextensions.annotations.lifecycle.Stop;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * @author Tomasz Krzyzak, <a
 *         href="mailto:tomasz.krzyzak@gmail.com">tomasz.krzyzak@gmail.com</a>
 * @since 2009-07-30 22:35:47
 */
public class ExtensionPoint extends ConfigurableElem {

    private Set<Class<?>> defaultExtensionClasses = new HashSet<Class<?>>();
    private boolean started;

    public ExtensionPoint(SimpleXtensionsBundle bundle, Object executable, String id) {
        super(bundle, executable, id);
    }

    public ExtensionPoint(SimpleXtensionsBundle bundle, String clazz, String id, Class<?> defaultExtensionClasses[],
                          ExtensionStyle extensionStyle, String factoryMethod) {
        super(bundle, clazz, id, extensionStyle, factoryMethod);
        this.defaultExtensionClasses.addAll(Arrays.asList(defaultExtensionClasses));
    }

    /**
     * @return the extensionClass
     */
    public Set<Class<?>> getDefaultExtensionClasses() {
        return Collections.unmodifiableSet(defaultExtensionClasses);
    }

    private Object extensionInstance = null;

    public synchronized Object getExecutable() {
        if (extensionInstance == null)
            extensionInstance = super.getExecutable();
        return extensionInstance;
    }

    public boolean isStarted() {
        return started;
    }

    public void addExtension(Extension e) {
        Object executable1 = getExecutable();
        if (executable1 instanceof IExtensionPoint) {
            ((IExtensionPoint) executable1).addExtension(e);
        } else {
            runMethod(executable1, Add.class, new Class[]{Extension.class}, e);
        }
    }

    public void removeExtension(Extension e) {
        Object executable1 = getExecutable();
        if (executable1 instanceof IExtensionPoint) {
            ((IExtensionPoint) executable1).removeExtension(e);
        } else {
            runMethod(getExecutable(), Remove.class, new Class[]{Extension.class}, e);
        }
    }

    public void start() {
        Object executable1 = getExecutable();
        if (executable1 instanceof IExtensionPoint) {
            ((IExtensionPoint) executable1).start(this);
        } else {
            runMethod(getExecutable(), Start.class, new Class[]{ExtensionPoint.class}, this);
        }
        started = true;
    }

    public void stop() {
        if (started == false) {
            throw new IllegalStateException("cannot stop already stopped ExtensionPoint: " + getId());
        }
        Object executable1 = getExecutable();
        if (executable1 instanceof IExtensionPoint) {
            ((IExtensionPoint) executable1).start(this);
        } else {
            runMethod(getExecutable(), Stop.class, new Class[]{ExtensionPoint.class}, this);
        }
        started = false;
    }

    private void runMethod(Object executable1, Class<? extends Annotation> annotation, Class[] classes, Object... params) {
        Method start = findMethod(executable1.getClass(), annotation, classes);
        if (start != null) {
            try {
                start.invoke(executable1, params);
            } catch (Exception e) {
                throw new RuntimeException("while invoking start on: " + getId(), e);
            }
        }
    }

    private Method findMethod(Class inspected, Class<? extends Annotation> annotation, Class[] classes) {
        if (inspected == null)
            return null;

        for (Method m : inspected.getDeclaredMethods()) {
            if (m.getAnnotation(annotation) != null && Arrays.equals(m.getParameterTypes(), classes)) {
                return m;
            }
        }
        return findMethod(inspected.getSuperclass(), annotation, classes);
    }


}
